package top.haijunit.common.web.utils;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Optional;

/**
 * The type Spring web utils.
 *
 * @author zhanghaijun
 * @date 2024 /2/29 14:21
 * @description web工具类
 */
@Slf4j
@UtilityClass
public class SpringWebUtils extends org.springframework.web.util.WebUtils {

    private final String BASIC_ = "Basic ";

    /** 获取response对象 */
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Assert.notNull(requestAttributes, "response from RequestContextHolder is null");
        return requestAttributes.getResponse();
    }

    /** 获取request对象 */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Assert.notNull(requestAttributes, "request from RequestContextHolder is null");
        return requestAttributes.getRequest();
    }

    /**
     * 获取请求路径
     * 例如：
     * &nbsp;&nbsp;controller: @GetMapping("/pd/{storageKey}/**")
     * &nbsp;&nbsp;请求地址：/pd/storageKey/a/b/c
     * &nbsp;&nbsp;获取到的地址：a/b/c
     */
    public static String getExtractPathWithinPattern() {
        HttpServletRequest httpServletRequest = getRequest();
        String path = (String) httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        String bestMatchPattern = (String) httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        return new AntPathMatcher().extractPathWithinPattern(bestMatchPattern, path);
    }

    /** 获取客户端IP地址 cn.hutool.extra.servlet.ServletUtil.getClientIP(getRequest()); */
    public static String getClientIp() {
        HttpServletRequest request = getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.contains(",")) {
                ip = ip.split(",")[0];
            }
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 从请求头中解析 client id
     */
    public Optional<String> extractClientId(String header) {
        return Optional.ofNullable(extractClientId(header, null));
    }

    /**
     * 解析 client id
     *
     * @return 如果解析失败返回默认值
     */
    public String extractClientId(String header, final String defVal) {
        if (header == null || !header.startsWith(BASIC_)) {
            log.debug("请求头中client信息为空: {}", header);
            return defVal;
        }
        byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            log.debug("Failed to decode basic authentication token: {}", header);
            return defVal;
        }
        String token = new String(decoded, StandardCharsets.UTF_8);
        int delim = token.indexOf(":");
        if (delim == -1) {
            log.debug("Invalid basic authentication token: {}", header);
            return defVal;
        }
        return token.substring(0, delim);
    }

    /** 判断请求头中是否包含某个字段 */
    public boolean headerContain(String name) {
        Enumeration<String> headerNames = getRequest().getHeaderNames();
        while (headerNames.hasMoreElements()) {
            if (StrUtil.equalsIgnoreCase(headerNames.nextElement(), name)) {
                return true;
            }
        }
        return false;
    }

    /** 读取cookie */
    public String getCookieValue(String name) {
        Cookie cookie = getCookie(getRequest(), name);
        return cookie != null ? cookie.getValue() : null;
    }

    /** 清除 某个指定的cookie */
    public void removeCookie(String key) {
        setCookie(getResponse(), key, null, 0);
    }

    /** 设置cookie */
    public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
        Cookie cookie = new Cookie(name, value);
        cookie.setPath("/");
        cookie.setMaxAge(maxAgeInSeconds);
        cookie.setHttpOnly(true);
        response.addCookie(cookie);
    }

    /**
     * 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解
     *
     * @param handlerMethod HandlerMethod
     * @return 是否ajax请求
     */
    public boolean isBody(HandlerMethod handlerMethod) {
        ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);
        return responseBody != null;
    }

    /** 获取请求参数 */
    private String getParameter(String name) {
        return SpringWebUtils.getRequest().getParameter(name);
    }

    /**
     * 文件支持分块下载和断点续传
     *
     * @param filename    the filename
     * @param inputStream the storage url
     * @param size        the size
     * @param contentType the content type
     */
    public static void chunkDownload(String filename, InputStream inputStream, Long size, String contentType) {
        String username = "匿名用户";
        HttpServletRequest request = SpringWebUtils.getRequest();
        HttpServletResponse response = SpringWebUtils.getResponse();
        String range = request.getHeader("Range");
        // 开始下载位置
        long startByte = 0;
        // 结束下载位置
        long endByte = size - 1;
        log.debug("文件[{}]开始位置：{}，文件结束位置：{}，文件总长度：{}", filename, startByte, endByte, size);
        // 有range的话
        if (range != null && range.contains("bytes=") && range.contains("-")) {
            range = range.substring(range.lastIndexOf("=") + 1).trim();
            String[] ranges = range.split("-");
            try {
                // 判断range的类型
                if (ranges.length == 1) {
                    // 类型一：bytes=-2343
                    if (range.startsWith("-")) {
                        endByte = Long.parseLong(ranges[0]);
                    }
                    // 类型二：bytes=2343-
                    else if (range.endsWith("-")) {
                        startByte = Long.parseLong(ranges[0]);
                    }
                }
                // 类型三：bytes=22-2343
                else if (ranges.length == 2) {
                    startByte = Long.parseLong(ranges[0]);
                    endByte = Long.parseLong(ranges[1]);
                }
                // http状态码要为206：表示获取部分内容
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            } catch (NumberFormatException e) {
                startByte = 0;
                endByte = size - 1;
                log.error("Range Occur Error,Message:{}", e.getLocalizedMessage());
            }
        }
        // 要下载的长度
        long contentLength = endByte - startByte + 1;
        // 各种响应头设置
        // 支持断点续传，获取部分字节内容：
        response.setHeader("Accept-Ranges", "bytes");
        response.setContentType(contentType);
        // inline表示浏览器直接使用，attachment表示下载，fileName表示下载的文件名, 解决下载文件时文件名乱码问题
        String contentDisposition = String.format("inline;filename=%s", new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
        response.setContentLengthLong(contentLength);
        // Content-Range，格式为：[要下载的开始位置]-[结束位置]/[文件总大小]
        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + size);
        try (BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
            StreamUtils.copyRange(inputStream, outputStream, startByte, endByte);
            log.info(String.format("用户[%s]下载[ %s ]完毕：%d-%d", username, filename, startByte, endByte));
        } catch (IOException e) {
            log.info(String.format("用户[%s]下载[ %s ]IO异常，Message：{}", filename, e.getLocalizedMessage()));
        }
    }
}
